iT邦幫忙

2022 iThome 鐵人賽

DAY 4
0
Web 3

Smart-Contract Language: Move系列 第 4

Day 4 Expression and Scope

  • 分享至 

  • xImage
  •  

Local variable

使用 let 來宣告變量,且是可變的,可以直接更新。


let x = 1;
let y = x + x;

let z
z = true // 也可以稍後在分配值

使用前賦值

let x;
x + x // ERROR!

let x;
if (cond) x = 0;
x + x // ERROR!

有效的變量名

可以包含下劃線,大小寫英文字母,數字 0 to 9

必須以下劃線或字母開頭 (不能大寫字母)

// all valid
let x = e;
let _x = e;
let _A = e;
let x0 = e;
let xA = e;
let foobar_123 = e;

// all invalid
let X = e; // ERROR!
let Foo = e; // ERROR!

類型註解

Move 擁有自動推斷型別能力,但也允許顯示類型註釋,提高易讀性。

而當無法自動推斷類型時,則需要自行添加類型

let x: T = e; // 變數 x 的型別是 T

let _v1 = Vector::empty(); // ERROR!
//        ^^^^^^^^^^^^^^^ Could not infer this type. Try adding an annotation
let v2: vector<u64> = Vector::empty(); // no error

帶有元組 (Tuple) 的多個聲明

let 可以使用元組

let () = ();
let (x0, x1) = (0, 1);
let (y0, y1, y2) = (0, 1, 2);
let (z0, z1, z2, z3) = (0, 1, 2, 3);

// 數量必需一致
let (x, y) = (0, 1, 2); // ERROR!
let (x, y, z, q) = (0, 1, 2); // ERROR!

帶有結構的多個聲明

let 也可以搭配 struct,用法如下

struct 之後會介紹給大家

let T { f1: u64, f2: u64 } = T { f1: 1, f2: 2 };

針對引用進行解構

在上面的結構示範中,let 中的綁定值被移動,破壞了結構值並綁定了他的 field

如果希望不移動和破壞結構值,可以使用 &

& 代表不可以修改

let T { f1: local1, f2: local2 } = T { f1: 1, f2: 2 };
// local1: u64
// local2: u64

let t = T { f1: 1, f2: 2 };
let T { f1: local1, f2: local2 } = &t;
// local1: &u64
// local2: &u64

忽略值

以開頭 _ 的變量,將被忽略,不會引入新變量

fun three(): (u64, u64, u64) {
    (0, 1, 2)
}

let (x1, _, z1) = three(); // x1 = 0, z1 = 2
let (x2, _y, z2) = three(); // x1 = 0, z1 = 2

Schema

我們來詳細看一下 let 結構

一般宣告時,整個表達式就是 let-binding 包含了左側的 local variable, 和右側的 initializer 值,type annotation 則會左側的 : 符號之後

結構宣告時,相比一般宣告,多了幾個部分

左測結構中,每個 field 會進行所謂 field-binding,將右側的 initializer 綁定過去

    let (x, y): (u64, u64) = (0, 1);
//       ^                           local-variable
//       ^                           pattern
//          ^                        local-variable
//          ^                        pattern
//          ^                        pattern-list
//       ^^^^                        pattern-list
//      ^^^^^^                       pattern-or-list
//            ^^^^^^^^^^^^           type-annotation
//                         ^^^^^^^^  initializer
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ let-binding

    let Foo { f, g: x } = Foo { f: 0, g: 1 };
//      ^^^                                    struct-type
//            ^                                field
//            ^                                field-binding
//               ^                             field
//                  ^                          local-variable
//                  ^                          pattern
//               ^^^^                          field-binding
//            ^^^^^^^                          field-binding-list
//      ^^^^^^^^^^^^^^^                        pattern
//      ^^^^^^^^^^^^^^^                        pattern-or-list
//                      ^^^^^^^^^^^^^^^^^^^^   initializer
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ let-binding

通過引用進行變異 (mutating)

除了通過賦值直接修改變量外,還可以通過可變引用來修改局部&mut

&mut 代表可以修改

let x = 0;
let r = &mut x;
*r = 1;

Debug::print<u8>(&x); // x is 1
}

在以下情況特別有用

  1. 根據某些條件修改本地值

    let x = 0;
    let y = 1;
    let r = if (cond) &mut x else &mut y;
    *r = *r + 1;
    
    Debug::print<u8>(&x); // x is 1
    
  2. 透過另一個函數來修改變量

    let x = 0;
    modify_ref(&mut x);
    

Scope

每組 { } 就是一個範圍,範圍的存取只能向內嵌套作用,不能向外。

{
    let x = 0;
    {
        let y = x + 1; // valid
    }
}
let x = 0;
{
    let y = 1;
};
x + y // ERROR!
//  ^ unbound local 'y'

下劃線表示未使用

script {
    fun main() {
        let _ = 1;
    }
}

陰影

如果 let 使用了一個已經在範圍內的變量名字,則該範圍的其餘部分將無法在訪問先前的變量,稱為陰影。且不用遵照前者的類型

要注意得是,陰影發生後,儲存在本地的值依然存在,但將不在可訪問。

let x = 0;

let x = true; // x is shadowed, the value is true, nothing error

表達式塊

是由分號分隔的一系列語句,表達式塊的結果值是區塊內最後一個表達式的值

{ let x = 1; let y = 1; x + y } // 塊的結果是 x + y

{ let x; let y = 1; x = 1; x + y } // 可以是 let 聲明或是使用表達式

函數調用是 type 的另一種常見表達方式

{ let v = Vector::empty(); Vector::push_back(&mut v, 1); v }

移動和複製

在 Move 中,所有變量都可以使用兩種方式

  1. 移動 Move
    1. 將原本值的記憶體位置內容,完全移動到目標變量的記憶體位置
  2. 複製 Copy
    1. 複製一份內容到目標變量的記憶體位置

若未指定,Move 會自動推斷使用

// 使用複製
let x = 0;
let y = copy x + 1;
let z = copy x + 2;

// 使用移動
let x = 1;
let y = move x + 1;
//      ------ Local x was moved here
let z = move x + 2; // Error!
//      ^^^^^^ Invalid usage of local 'x'
y + z

安全

Move 的類型系統會阻止一個值在移動後被使用,與 let 聲明描述檢查相同,可防止變量在賦值之前被使用


Basic Expression

代表返回值的代碼,在 Move 裡,必須在每個表達式後加上 ; ,否則會無法編譯

空表達式

script {
    fun empty() {
        () // this is an empty expression
    }
}

變數儲存

可以使用 let 宣告變量來儲存表達式的值

script {
    fun main() {
        let a; // 如果拿掉 ; 將會錯誤
        let b = true;
        let c = 10;
        let d = 0x1;
    }
}

本篇從本地變量開始,介紹類型註釋和多種聲明,以及 Scope 的範圍,接著進入表達式和如何使用。而有些東西像是 & , &mut 和自動推斷因為篇幅關係,我們在後面會專門做介紹。讓我們 Move to Day5

相關資料

Tuple: https://en.wikipedia.org/wiki/Tuple

Shadowing: https://en.wikipedia.org/wiki/Variable_shadowing


上一篇
Day 3 Primitive Types
下一篇
Day 5 Constants & Import
系列文
Smart-Contract Language: Move30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言